import axios, { AxiosHeaders } from "axios";
import qs from "qs";
// import $loading from "../utils/element/loading";
import { $singleMsg } from "xt-public/plugins/utils/element.ts";
import { filterData, standardizedUrl } from "./tools";
import { isDev } from "../utils";
import { getCookie } from "../utils/cookie";
import type Typings from "@/typings";

// 请求方法类型
type RequestMethod =
  | "post"
  | "get"
  | "put"
  | "delete"
  | "POST"
  | "GET"
  | "PUT"
  | "DELETE";

// 请求 config 配置参数类型
type RequestConfig = Typings.Common.plainObj & {
  timeout?: number;
  openLoading?: boolean;
};

// 请求取消 Source 类型
type SourceType = {
  url: string;
  method: RequestMethod;
  data: Typings.Common.plainObj;
  source: any;
  cancel?: any;
};

// 请求队列（axios.CancelToken.source）数组
export let sourceQueue: SourceType[] = [];

// 设置接口取消
export const cancelRequest = (urls: string[]) => {
  sourceQueue = sourceQueue.filter((item) => {
    if (urls.includes(item.url)) {
      // 取消请求
      item.source.cancel("用户主动取消了请求");
      return false;
    }
    return true;
  });
};

/**
 * 请求时开启的 loading
 * @param type
 * @param config
 */
const setLoadingStatus = (
  type: "show" | "close" = "show",
  config: RequestConfig = {}
) => {
  if (type === "close") {
    // $loading.close();
  } else if (type === "show" && config.openLoading) {
    // $loading.show();
  }
};

class Http {
  static maxRetryTimes: number = 3; // 请求失败最大重试次数
  static defaultTimeout: number = 1000 * 6; //  响应超时时限
  static RESPONSE_OK_CODE: number = 0; // 请求正常响应时的接口返回 code 值，默认 0
  static ErrorStatusFn: Typings.Common.plainObj = {
    401: () => {
      console.warn("接口请求失败，状态 401 钩子未匹配对应函数");
    },
  };

  url: string;
  data: Typings.Common.plainObj;
  config: RequestConfig;
  currentRetryCount: number;
  method: RequestMethod;
  timeout: number;
  axiosSource: SourceType | null;
  defaultBaseURL: string;

  // 初始化
  constructor(
    url: string = "",
    data: Typings.Common.plainObj = {},
    config: Typings.Common.plainObj
  ) {
    // 请求路径
    this.url = url;
    // 处理请求参数
    this.data = filterData(data);
    this.config = {
      // 默认 headers
      headers: { "Content-Type": "json" },
      ...config,
    };
    // 记录当前实例重试次数，默认 0 次
    this.currentRetryCount = 0;
    // 默认请求方式
    this.method = "post";
    // 默认请求超时时间
    this.timeout = config?.timeout || Http.defaultTimeout; // 响应超时时限
    // 取消请求 source
    this.axiosSource = null;
    // 请求接口时，添加请求前缀
    this.defaultBaseURL = "/api";
  }

  /**
   * Http 类的方法
   * 默认处理 GET 请求的 params 参数，可以被继承覆盖
   * @returns
   */
  convertParams() {
    return {};
  }

  /**
   * Http 类的方法
   * 默认处理 POST 请求的 data 参数，可以被继承覆盖
   * @returns
   */
  convertData() {
    return {};
  }

  /**
   * Http 类的方法
   * 默认处理请求头的 headers 参数，可以继承覆盖
   * @returns
   */
  convertHeaders: () => Promise<any> = async () => {
    const headers: Partial<AxiosHeaders> = {};

    switch (this.config.headers["Content-Type"]) {
      case "json":
        headers["Content-Type"] = "application/json;charset=utf-8";
        break;
      case "formType":
        headers["Content-Type"] =
          "application/x-www-form-urlencoded;charset=utf-8";
        break;
      case "formData":
        headers["Content-Type"] = "multipart/form-data;charset=utf-8";
        break;
      default:
        headers["Content-Type"] = "application/json;charset=utf-8";
        break;
    }

    headers.authorization = getCookie("token");
    return headers;
  };

  /**
   * Http 类的方法
   * 默认处理请求 url 路径，可以继承覆盖
   * @returns
   */
  convertUrl: () => string = () => {
    // 获取实际的 baseURL
    const trueBaseURL: string = this.config.baseURL
      ? standardizedUrl(this.config.baseURL)
      : standardizedUrl(this.defaultBaseURL);

    // 开发环境请求前添加 /API
    const envFlag = isDev ? "/API" : "";

    // 返回最终的请求路径
    return `${envFlag}${trueBaseURL}${standardizedUrl(this.url)}`;
  };

  /**
   * Http 类的方法
   * 没退出 while 循环时不弹出错误信息
   * @param text
   * @param force
   */
  setMsg(text: string = "", force: boolean = false) {
    if (force || this.currentRetryCount >= Http.maxRetryTimes) {
      $singleMsg({ type: "error", message: text });
    }
  }

  // 核心方法，请求封装
  async _request() {
    try {
      /**
       * 创建接口取消 source
       * 并添加到请求队列 sourceQueue 中
       */
      this.axiosSource = {
        url: this.url,
        source: axios.CancelToken.source(),
        method: this.method,
        data: this.data,
      };

      sourceQueue.push(this.axiosSource);

      // 开启 loading
      setLoadingStatus("show", this.config);

      // 发起请求
      const res = await axios({
        method: this.method,
        url: this.convertUrl(),
        params: this.convertParams(),
        data: this.convertData(),
        headers: await this.convertHeaders(),
        timeout: this.timeout,
        cancelToken: this.axiosSource.source.token,
        responseType: this.config.responseType || "json",
      });

      return res;
    } catch (error: unknown) {
      /**
       * 请求报错时，返回 Promise.reject
       * 供后续对错误 error 的处理
       */
      return Promise.reject(error);
    } finally {
      // 本次请求完成后将 this.axiosSource 从请求队列 sourceQueue 中删掉
      sourceQueue = sourceQueue.filter((item) => item !== this.axiosSource);
      // 清空当前接口取消 source
      this.axiosSource = null;
      // 取消 loading
      setLoadingStatus("close");
    }
  }

  // 接口请求正常时，返回接口响应的数据
  responseSuccess(res: any) {
    /**
     * 请求成功
     * 接口返回 res.data.code 不是正常 code 时报错处理，提示 res.data.msg || 接口报错
     */
    if (
      ![Http.RESPONSE_OK_CODE].includes(res.data.code) &&
      this.config.responseType !== "blob"
    ) {
      this.setMsg(res.data.msg || "接口报错", true);
      return Promise.reject(res.data.msg);
    }

    /**
     * 接口返回 res.data.code 是正常 code 时，判断是否为下载文件
     * 文件返回整个 res，不是文件返回 res.data
     */
    return this.config.responseType === "blob" ? res : res.data;
  }

  /**
   * 开始请求
   * while 循环处理接口重试逻辑（目前只有请求超时时，才会重试接口）
   * 请求正常时，return 可以断开 while 循环，函数返回正常响应的接口 return this.responseSuccess(res);
   * 请求异常且没有匹配到异常情况时，函数返回 undefined
   * @returns
   */
  async request() {
    while (++this.currentRetryCount <= Http.maxRetryTimes) {
      try {
        // 调用封装好的请求
        const res = await this._request();

        /**
         * 请求未出错时，返回响应结果
         * return 退出 while 循环
         */
        return this.responseSuccess(res);
      } catch (error: any) {
        /**
         * 是否请求超时判定
         * 需要判重复调用接口，不处理 this.currentRetryCount while 循环中自行累加）
         */
        const isTimeoutError: boolean =
          error.code === "ECONNABORTED" &&
          error.message &&
          error.message.includes("timeout");

        /**
         * 是否请求取消判定
         * 默认取消时 error.message 返回 canceled
         * 这里取对我们自定义的取消 error.message = cancel-by-self 做拦截，调用时 xxx.cancel("cancel-by-self")
         * 自定义取消时会跳出 while 循环
         * 默认取消时只会取消当次请求，不会跳出 while 循环
         */
        const isCancelError: boolean =
          error.code === "ERR_CANCELED" &&
          error.message &&
          error.message.includes("cancel-by-self");

        /**
         * 是否为请求节流报错判定
         * 不重复调用接口，将 this.currentRetryCount 置为 Http.maxRetryTimes, 退出 while 循环
         */
        const isRequestThrottleError: boolean =
          error.message === "request throttle";

        /**
         * 是否为防抖请求报错判定 request debounce
         * 不重复调用接口，将 this.currentRetryCount 置为 Http.maxRetryTimes, 退出 while 循环
         */
        const isRequestDebounceError: boolean =
          error.message === "request debounce";

        /**
         * 自定义 http 状态码报错
         */
        const isStatusError: boolean = error.response;

        if (isTimeoutError) {
          /**
           * case 1 请求超时
           * 响应超时处理
           * 需要判重复调用接口，不处理 this.currentRetryCount while 循环中自行累加）
           */
          this.setMsg("响应超时！");

          // 最后一次时返回 Promise.reject, 退出 while 循环
          if (this.currentRetryCount === Http.maxRetryTimes) {
            return Promise.reject(error.message);
          }
        } else if (isCancelError) {
          /**
           * case 2 取消请求
           * 自行取消接口
           * 不重复调用接口，将 this.currentRetryCount 置为 Http.maxRetryTimes, 退出 while 循环
           */
          this.currentRetryCount = Http.maxRetryTimes;

          return Promise.reject(error.message);
        } else if (isRequestThrottleError) {
          /**
           * case 3 请求节流报错
           * 不重复调用接口，将 this.currentRetryCount 置为 Http.maxRetryTimes, 退出 while 循环
           */
          this.currentRetryCount = Http.maxRetryTimes;
          return Promise.reject(error.message);
        } else if (isRequestDebounceError) {
          /**
           * case 4 取消请求
           * 自行取消接口
           * 不重复调用接口，将 this.currentRetryCount 置为 Http.maxRetryTimes, 退出 while 循环
           */
          this.currentRetryCount = Http.maxRetryTimes;

          return Promise.reject(error.message);
        } else if (isStatusError) {
          /**
           * case 5 自定义 http 状态码报错
           * http状态码错误处理
           * 不重复调用接口，将 this.currentRetryCount 置为 Http.maxTryTimes, 退出 while 循环
           */
          this.currentRetryCount = Http.maxRetryTimes;

          // 当前请求的响应 http 状态码
          const status: number = error.response.status;

          if ([401].includes(status)) {
            while (sourceQueue.length) {
              // 移除请求队列中 cancelToken-authorizationInvalid 的类型， 取消队列中的所有请求
              (<SourceType>sourceQueue.pop()).cancel(
                "cancelToken-authorizationInvalid"
              );
            }

            Http.ErrorStatusFn[401] && (await Http.ErrorStatusFn[401]());
          } else if ([403].includes(status)) {
            this.setMsg(`请求接口无权限! code: ${status}`);
          } else if (/^[45]\d\d$/.test(status.toString())) {
            // 其它的400或者500错误的处理
            this.setMsg(`服务器异常，error code: ${status}`);
          }

          // 其他报错情况，退出 while 循环
          return Promise.reject(error.message);
        } else {
          console.log(error);
          return Promise.reject("未知报错");
        }
      }
    }
  }
}

/**
 * 继承 POST 请求类
 */
class PostHttp extends Http {
  constructor(
    url: string,
    data: Typings.Common.plainObj,
    config: RequestConfig
  ) {
    super(url, data, config);
    this.method = "post";
  }

  convertData: () => string | object = () => {
    if (this.config.headers["Content-Type"] === "formType") {
      // data序列化，对应content-type: 'application/x-www-form-urlencoded'
      return qs.stringify(this.data);
    } else {
      return this.data;
    }
  };
}

/**
 * PUT 请求类
 */
class PutHttp extends Http {
  constructor(
    url: string,
    data: Typings.Common.plainObj,
    config: RequestConfig
  ) {
    super(url, data, config);
    this.method = "put";
  }

  convertData() {
    if (this.config.ContentType === "formType") {
      // data序列化，对应content-type: 'application/x-www-form-urlencoded'
      return qs.stringify(this.data);
    } else {
      return this.data;
    }
  }
}

/**
 * GET 请求类
 */
class GetHttp extends Http {
  constructor(
    url: string,
    data: Typings.Common.plainObj,
    config: RequestConfig
  ) {
    super(url, data, config);
    this.method = "get";
  }

  convertParams: () => any = () => {
    const data: any = { ...this.data };
    // 非ie浏览器，不需要加url上加_t
    if (!(!!(window as any).ActiveXObject || "ActiveXObject" in window)) {
      return data;
    }
    // ie浏览器,url加上_t参数,防止ie浏览器get请求缓存
    if (!Object.prototype.hasOwnProperty.call(this.data, "_t")) {
      data._t = Date.now();
    } else {
      data._t_t_t_t = Date.now();
    }
    return data;
  };
}

/**
 * DELETE 请求类
 */
class DeleteHttp extends Http {
  constructor(
    url: string,
    data: Typings.Common.plainObj,
    config: RequestConfig
  ) {
    super(url, data, config);
    this.method = "delete";
  }

  convertParams() {
    return this.data;
  }
}

export default {
  async post(
    url: string,
    data: Typings.Common.plainObj,
    config: RequestConfig
  ) {
    return new PostHttp(url, data, config).request();
  },
  put(url: string, data: Typings.Common.plainObj, config: RequestConfig) {
    return new PutHttp(url, data, config).request();
  },
  get(url: string, data: Typings.Common.plainObj, config: RequestConfig) {
    return new GetHttp(url, data, config).request();
  },
  delete(url: string, data: Typings.Common.plainObj, config: RequestConfig) {
    return new DeleteHttp(url, data, config).request();
  },
};

// 设置响应超时默认值
export const setDefaultTimeout = (timeStamps: number) => {
  // 这里修改的是 Http 类的请求超时时间，单个接口的修改可以通过接口 config 参数的 timeout 来修改
  Http.defaultTimeout = timeStamps;
};

// 设置响应超时重试最大次数
export const setDefaultMaxRetryTimes = (times: number) => {
  Http.maxRetryTimes = times;
};

// 设置请求成功时，接口响应数据正常的 code
export const setResponseOkCode = (code: number) => {
  Http.RESPONSE_OK_CODE = code;
};

// 设置请求失败错误的执行方法（方便解耦）
export const setErrorStatusFn = (obj: Typings.Common.plainObj = {}) => {
  Http.ErrorStatusFn = {
    ...Http.ErrorStatusFn,
    ...obj,
  };
};
